#include "PreCompiled.h"
#include "Document_App.h"
#include "DocumentObject.h"
#include "Application_App.h"
#include "Console.h"
#include "Tools.h"


#include "Console.h"
#include "FileInfo.h"
#include "TimeInfo.h"
#include "Reader.h"
#include "Writer.h"
#include "Uuid.h"

#include "zipios++/zipios-config.h"
#include "zipios++/zipfile.h"
#include "zipios++/zipinputstream.h"
#include "zipios++/zipoutputstream.h"
#include "zipios++/meta-iostreams.h"

using Base::Console;
using Base::Writer;
using namespace App;
using namespace std;
using namespace boost;
using namespace zipios;

struct AppDocumentP
{
	std::vector<App::DocumentObject*> objectArray;
	std::map<std::string,App::DocumentObject*>objectMap;
	App::DocumentObject* activeObject;
	AppDocumentP()
	{
		activeObject = 0;
	}

};



PROPERTY_SOURCE(Document_App, App::PropertyContainer)
Document_App::Document_App(void)
{
	d = new  AppDocumentP;
}

Document_App::~Document_App(void)
{
	d->objectArray.clear();
	for(std::map<std::string,App::DocumentObject*>::iterator it = d->objectMap.begin(); it != d->objectMap.end(); ++it)
	{
		delete it->second;
	}
}
App::DocumentObject *Document_App::getObject(const char *Name) const
{
	std::map<std::string,App::DocumentObject*>::const_iterator it = d->objectMap.find(Name);
	return (it != d->objectMap.end() ? it->second : NULL);
}
/// Returns a Name of an Object or 0
const char *Document_App::getObjectName(DocumentObject *pFeat) const
{
    return "";
}
const char* Document_App::getName()const
{
	return GetApplication().getDocumentName(this);
}

void Document_App::Save(Base::Writer &writer)const
{

}
void Document_App::Restore(Base::XMLReader &reader)
{

}
void Document_App::setFileValue(const char* value)
{
	FileName.setValue(value);
}
void Document_App::setlabelValue(const char* value)
{
    Label.setValue(value);
}
bool Document_App::isSaved()const
{
	std::string name = FileName.getValue();
	return !name.empty();
}
bool Document_App::save()
{
// 	int compression = GetApplication().GetParameterGroupByPath
// 		("User parameter:BaseApp/Preferences/Document")->GetInt("CompressionLevel",3);

	int compression = 3;

	if (*(FileName.getValue()) != '\0') {
		LastModifiedDate.setValue(Base::TimeInfo::currentDateTimeString());
		// make a tmp. file where to save the project data first and then rename to
		// the actual file name. This may be useful if overwriting an existing file
		// fails so that the data of the work up to now isn't lost.
		std::string uuid = Base::Uuid::createUuid();
		std::string fn = FileName.getValue();
		fn += "."; fn += uuid;
		Base::FileInfo tmp(fn);

		// open extra scope to close ZipWriter properly
		{
			Base::ofstream file(tmp, std::ios::out | std::ios::binary);
			Base::ZipWriter writer(file);

			writer.setComment("FreeCAD Document");
			writer.setLevel(compression);
			writer.putNextEntry("Document.xml");

			Document_App::Save(writer);

			// Special handling for Gui document.
			signalSaveDocument(writer);

			// write additional files
			writer.writeFiles();

			GetApplication().signalSaveDocument(*this);
		}

		// if saving the project data succeeded rename to the actual file name
		Base::FileInfo fi(FileName.getValue());
		if (fi.exists()) {
			bool backup = GetApplication().GetParameterGroupByPath
				("User parameter:BaseApp/Preferences/Document")->GetBool("CreateBackupFiles",true);
			int count_bak = GetApplication().GetParameterGroupByPath
				("User parameter:BaseApp/Preferences/Document")->GetInt("CountBackupFiles",1);
			if (backup) {
				int nSuff = 0;
				std::string fn = fi.fileName();
				Base::FileInfo di(fi.dirPath());
				std::vector<Base::FileInfo> backup;
				std::vector<Base::FileInfo> files = di.getDirectoryContent();
				for (std::vector<Base::FileInfo>::iterator it = files.begin(); it != files.end(); ++it) {
					std::string file = it->fileName();
					if (file.substr(0,fn.length()) == fn) {
						// starts with the same file name
						std::string suf(file.substr(fn.length()));
						if (suf.size() > 0) {
							std::string::size_type nPos = suf.find_first_not_of("0123456789");
							if (nPos==std::string::npos) {
								// store all backup files
								backup.push_back(*it);
								nSuff = std::max<int>(nSuff, std::atol(suf.c_str()));
							}
						}
					}
				}

				if (!backup.empty() && (int)backup.size() >= count_bak) {
					// delete the oldest backup file we found
					Base::FileInfo del = backup.front();
					for (std::vector<Base::FileInfo>::iterator it = backup.begin(); it != backup.end(); ++it) {
						if (it->lastModified() < del.lastModified())
							del = *it;
					}

					del.deleteFile();
					fn = del.filePath();
				}
				else {
					// create a new backup file
					std::stringstream str;
					str << fi.filePath() << (nSuff + 1);
					fn = str.str();
				}

				fi.renameFile(fn.c_str());
			}
			else {
				fi.deleteFile();
			}
		}
		if (tmp.renameFile(FileName.getValue()) == false)
			Base::Console().Warning("Cannot rename file from '%s' to '%s'\n",
			fn.c_str(), FileName.getValue());

		return true;
	}

	return false;
}
App::DocumentObject* Document_App::addObject(const char* sType,const char* pObjectName)
{
 	Base::BaseClass* base = static_cast<Base::BaseClass*>(Base::Type::createInstanceByName(sType,true));
 	if(!base)
 		return 0;
	const char * type = base->getTypeId().getName();
 	std::string objectName;
 	if(!base->getTypeId().isDerivedFrom(App::DocumentObject::getClassTypeId()))
 	{
 		Base::Console().Error("%s is not a document object type",pObjectName);
 		return 0;
 	}
	App::DocumentObject* pObject = static_cast<App::DocumentObject*>(base);

    pObject->setDocument(this);
	

	if(pObjectName && pObjectName[0] != '\0')
		objectName = getUniqueObjectName(pObjectName);
	else
		objectName = getUniqueObjectName(sType);
     d->activeObject = pObject;

	d->objectMap[objectName] = pObject;

	pObject->_pcNameInDocumet = &(d->objectMap.find(objectName)->first);

    d->objectArray.push_back(pObject);

	pObject->Label.setValue(objectName);

	pObject->StatusBis.set(2);

	signalNewObject(*pObject);

	signalActiveObject(*pObject);

	return pObject;

}

std::string Document_App::getUniqueObjectName(const char* name)const
{
	if (!name || *name == '\0')
		return std::string();
	std::string CleanName = Base::Tools::getIdentifier(name);

	// name in use?
	std::map<std::string,DocumentObject*>::const_iterator pos;
	pos = d->objectMap.find(CleanName);

	if (pos == d->objectMap.end()) {
		// if not, name is OK
		return CleanName;
	}
	else {
		std::vector<std::string> names;
		names.reserve(d->objectMap.size());
		for (pos = d->objectMap.begin();pos != d->objectMap.end();++pos) {
			names.push_back(pos->first);
		}
		return Base::Tools::getUniqueName(CleanName, names, 3);
	}
}

